//TODO:better drawing/PosAll handling mIsDrawing,mIsPosValid
#include <buola/gui.h>

#include <buola/gui/cmenu.h>
#include <buola/gui/cicon.h>
#include <buola/gui/cconfig.h>
#include <buola/image/cimage.h>

#include <buola/widgets/cmenubar.h>
#include <buola/widgets/ctreebox.h>
#include <buola/gui/dnd/cdrop.h>
#include <buola/widgets/ctbrenderer.h>
#include <buola/widgets/ctheme.h>
#include <buola/widgets/cscrollbar.h>
#include <buola/widgets/ceditbox.h>

#include "ctbtreerenderer.h"

namespace buola { namespace gui {

CTreeBox::CTreeBox()
    :   mScrollPos(0,0)
{
    SetStyle(STYLE_DEFAULTVIEW);

    mDragging=false;
    mSelecting=false;
    mEditing=nullptr;

    mCursorNode=nullptr;

    mIconSize=CIcon::SMALL;

    mEmptyText=L"-- empty --";

    mAutoUpdate=true;

    mDropping.mInside=false;
    mDropping.mLast=nullptr;

    SetFocusMode(EFocusMode::MOUSE|EFocusMode::TAB);

    SetBackColor({1.0,1.0,1.0});

    SetPointer(CPointer::STOCK_DEFAULT);

    mHScroll=new CScrollBar();
    mHScroll->eChanged.Connect(&CTreeBox::OnHScroll,this,_1);
    mHScroll->SetStyle(CScrollBar::STYLE_HORIZONTAL);
    mHScroll->SetRange(0,15);
    mHScroll->SetStep(2,10);
    mHScroll->SetValue(0,false);

    mVScroll=new CScrollBar();
    mVScroll->eChanged.Connect(&CTreeBox::OnVScroll,this,_1);
    mVScroll->SetRange(0,15);
    mVScroll->SetStep(2,10);
    mVScroll->SetValue(0,false);

    Attach(mHScroll,mVScroll);

    AddDropWindow(this);
    SetPreferredSize({200,200});
}

CTreeBox::~CTreeBox()
{
}

///////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////
////////////////////////// Interface //////////////////////////
///////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////

void CTreeBox::EnsureVisible(SNode *pItem,bool pMore)
{
    CRect_d lRect(mRenderer->GetNodeRect(pItem));

    CPoint_d lScrollBy(0,0);

    CSize_d lMargin(Size().x-lRect.w(),Size().y-lRect.h());

    if(lMargin.x<0||!pMore) lMargin.x=0;
    if(lMargin.y<0||!pMore) lMargin.y=0;

    if(lRect.l<0)
    {
        lScrollBy.x-=(-lRect.l)+lMargin.x/2;
    }
    else if(lRect.r>Size().x)
    {
        lScrollBy.x+=(lRect.r-Size().x)+lMargin.x/2;
    }
    if(lRect.t<0)
    {
        lScrollBy.y-=(-lRect.t)+lMargin.y/2;
    }
    else if(lRect.b>Size().y)
    {
        lScrollBy.y+=(lRect.b-Size().y)+lMargin.y/2;
    }

    if(lScrollBy.x==0&&lScrollBy.y==0) return;

    CPoint_d lNewPos=mScrollPos+lScrollBy;

    mHScroll->SetValue((int)lNewPos.x);
    mVScroll->SetValue((int)lNewPos.y);
}

void CTreeBox::SetIconSize(int pStyle)
{
    if(pStyle!=mIconSize)
    {
        mIconSize=pStyle;

        if(mRenderer)
            mRenderer->OnBigChange();
    }
}

void CTreeBox::SetAutoUpdate(bool pAutoUpdate)
{
    mAutoUpdate=pAutoUpdate;
}

void CTreeBox::Update()
{
    if(mRenderer)
        mRenderer->OnBigChange();
}

void CTreeBox::SetEmptyText(const std::wstring &pText)
{
    mEmptyText=pText;
}

void CTreeBox::EndEditing()
{
    mEditing->mText=mEditBox->GetCaption();
    mEditBox->Unmap();
    mEditing=nullptr;
    return;
}

void CTreeBox::CreateRenderer()
{
    mRenderer.reset(new CTBTreeRenderer(this));
}

///////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////
/////////////////////// Event handlers ////////////////////////
///////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////

void CTreeBox::OnExpose(const CExposeEvent &pE)
{
    if(!mRenderer)
        CreateRenderer();

    mRenderer->OnExpose(pE);
}

void CTreeBox::OnHScroll(int pPos)
{
    ///\todo
//     int lScrollBy=(int)mScrollPos.x-pPos;
         mScrollPos.x=pPos;
         Refresh();
//     if(lScrollBy)
//         ScrollBy(CPoint_d(lScrollBy,0));
//     mRenderer->OnXScrollChanged((int)mScrollPos.x);
}

void CTreeBox::OnVScroll(int pPos)
{
    ///\todo
//     int lScrollBy=(int)mScrollPos.y-pPos;
     mScrollPos.y=pPos;
     Refresh();
//     if(lScrollBy)
//         ScrollBy(CPoint_d(0,lScrollBy));
}

void CTreeBox::OnButtonDown(const CMouseEvent &pE)
{
    if(mEditing)
    {
        EndEditing();
    }

    if(pE.mButton==EMouseButton::LEFT)
    {
        if(pE.mClickCount>1)
        {
            if(pE.mClickCount==2)
            {
                int lMode;
                SNode *lItem=mRenderer->GetNodeAt(pE.mPoint,&lMode);

                if(lItem&&!(lMode&LBHT_EXPAND))
                {
                    eDblClick(lItem);
                }
            }
            return;
        }

        int lType;
        SNode *lItem=mRenderer->GetNodeAt(pE.mPoint,&lType);

        if(lItem)
        {
            EnsureVisible(lItem,false);
            if(lType&LBHT_EXPAND)
            {
                if(lItem->mExpanded)
                {
                    lItem->Collapse();
                }
                else
                {
                    lItem->Expand();
                }
            }
            else
            {
                if(GetStyle(STYLE_ONECLICK))
                    eDblClick(lItem);
                if(mCursorNode!=lItem)
                {
                    SNode *lOld=mCursorNode;
                    mCursorNode=lItem;
                    if(mRenderer)
                    {
                        if(lOld)
                            OnCursorChanged(lOld);
                        OnCursorChanged(mCursorNode);
                    }
                }
                if(pE.mMods&KEYMOD_CONTROL)
                {
                    ToggleNode(lItem);
                }
                else if((!lItem->mSelected)||mTWSelected>1)
                {
                    SelectNode(lItem);
                }
                else                                //Item already selected
                {
                    if(lType&LBHT_TEXT)
                    {
                        if(GetStyle(STYLE_EDITABLE))
                        {
                            mEditBox=mRenderer->Edit(lItem);
                            mEditBox->eEnter.Connect(&CTreeBox::EndEditing,this);
                            mEditing=lItem;
                        }
                    }
                }

                mDragging=true;
            }
        }
        else
        {
            UnselectAll();
            mSelecting=true;
        }

        mDragPoint=pE.mPoint;
        mLastDragPoint=pE.mPoint;
    }
    else if(pE.mButton==EMouseButton::RIGHT)
    {
        int lMode;
        SNode *lItem=mRenderer->GetNodeAt(pE.mPoint,&lMode);

        if(lMode&LBHT_SELECT)
        {
            if(lItem&&!lItem->mSelected)
            {
                SelectNode(lItem->mIndex);
            }

            if(GetStyle(STYLE_MENU))
            {
                PMenu lMenu=new CMenu;

                bool lItemMenu=lItem?true:false;

                if(!lItemMenu)
                {
                    if(GetStyle(STYLE_ICONMENU))
                    {
                        PMenu lSubMenu=lMenu->AddSubMenu(L"icon size");
                        lSubMenu->Add(new_menu_item(L"small"))->
                                      eTriggered.Connect(&CTreeBox::SetIconSize,this,CIcon::MEDIUM);
                        lSubMenu->Add(new_menu_item(L"medium"))->
                                      eTriggered.Connect(&CTreeBox::SetIconSize,this,CIcon::LARGE);
                        lSubMenu->Add(new_menu_item(L"large"))->
                                      eTriggered.Connect(&CTreeBox::SetIconSize,this,CIcon::LARGER);
                        lSubMenu->Add(new_menu_item(L"huge"))->
                                      eTriggered.Connect(&CTreeBox::SetIconSize,this,CIcon::LARGEST);
                    }

                    if(GetStyle(STYLE_VIEWMENU))
                    {
                        PMenu lSubMenu=lMenu->AddSubMenu(L"view style");
                        lSubMenu->Add(new_menu_item(L"array"))->
                            eTriggered.Connect((void(CTreeBox::*)(TStyle,TStyle))&CTreeBox::SetStyle,this,(int)STYLE_VIEW,(int)STYLE_TREEVIEW);
                        lSubMenu->Add(new_menu_item(L"list"))->
                            eTriggered.Connect((void(CTreeBox::*)(TStyle,TStyle))&CTreeBox::SetStyle,this,(int)STYLE_VIEW,(int)STYLE_TREEVIEW);
                        lSubMenu->Add(new_menu_item(L"detail"))->
                            eTriggered.Connect((void(CTreeBox::*)(TStyle,TStyle))&CTreeBox::SetStyle,this,(int)STYLE_VIEW,(int)STYLE_TREEVIEW);
                    }

                    if(GetStyle(STYLE_VIEWMENU|STYLE_ICONMENU)&&GetStyle(STYLE_USERMENU))
                        lMenu->AddSeparator();
                }

                OnFillMenu(lMenu,lItemMenu);

                if(GetStyle(STYLE_USERMENU))
                    sFillMenu(lMenu,lItem);

                if(lMenu->size())
                {
                    PMenuBar lMenuBar=new CMenuBar;
                    ///\todo lMenuBar->CreatePopup(this,lMenu,pE.mRootPoint);
                    lMenuBar->Map();
                }
            }
        }
    }
}

void CTreeBox::OnButtonUp(const CMouseEvent &pE)
{
    if(pE.mButton==EMouseButton::LEFT)
    {
        if(mDragging)
        {
            mDragging=false;
        }
        else if(mSelecting)
        {
            if(mLastDragPoint!=mDragPoint)
            {
//              mGC->SetFunction(GCF_INVERT);

//              mGC->DrawArea(mDragPoint.x,mDragPoint.y,mLastDragPoint.x,mLastDragPoint.y);

//              mGC->SetFunction();
            }

            mSelecting=false;
        }
    }
}

void CTreeBox::OnMouseMove(const CMouseEvent &pE)
{
    if(mDragging)
    {

    }
    else if(mSelecting)
    {
/*
        mGC->SetFunction(GCF_INVERT);

        if(mLastDragPoint!=mDragPoint)
        {
            mGC->DrawArea(mDragPoint.x,mDragPoint.y,mLastDragPoint.x,mLastDragPoint.y);
        }

        mLastDragPoint=pE.mPoint;

        mGC->SetFunction();

        CRect_d lRect(mDragPoint,mLastDragPoint);

        int lItems[GetItemCount()];
        int lNumItems=0;

        for(int i=0;i<GetItemCount();i++)
        {
            if(HitTestItem(mLBI[i],lRect))
                lItems[lNumItems++]=mLBI[i]->mIndex;
        }

        SelectNodes(lNumItems,lItems);

        mGC->SetFunction(GCF_INVERT);

        if(mLastDragPoint!=mDragPoint)
        {
            mGC->DrawArea(mDragPoint.x,mDragPoint.y,mLastDragPoint.x,mLastDragPoint.y);
        }

        mGC->SetFunction();
*/
    }
}

void CTreeBox::OnMouseWheel(const CMouseEvent &pE)
{
    if(pE.mScrollX!=0.0)
    {
        mHScroll->Scroll(pE.mScrollX*mHScroll->GetSmallStep());
    }
    if(pE.mScrollY!=0.0)
    {
        mVScroll->Scroll(pE.mScrollY*mVScroll->GetSmallStep());
    }
}

void CTreeBox::OnFocusIn(const CFocusEvent &pE)
{
}

void CTreeBox::OnFocusOut(const CFocusEvent &pE)
{
}

void CTreeBox::OnChar(const CKeyEvent &pE)
{
    if(pE.mChar=='+')
    {
        if(!mCursorNode) return;

        mCursorNode->Expand();
    }
    else if(pE.mChar=='-')
    {
        if(!mCursorNode) return;

        mCursorNode->Collapse();
    }
}

void CTreeBox::OnKeyDown(const CKeyEvent &pE)
{
    if(pE.mKey==KEY_UP||pE.mKey==KEY_DOWN)
    {
        if(!mCursorNode) return;
        SNode *lOther;
        if(pE.mKey==KEY_UP)
            lOther=mCursorNode->GetPreviousVisible();
        else
            lOther=mCursorNode->GetNextVisible();

        if(lOther)
        {
            if(pE.mMods&KEYMOD_SHIFT)
            {
                if(mCursorNode->mSelected)  //just extend
                {
                    if(lOther->mSelected)
                    {
                        ToggleNode(mCursorNode);
                    }
                    else
                    {
                        ToggleNode(lOther);
                    }
                }
                else
                {
                    std::vector<SNode*> lNodes;
                    lNodes.push_back(mCursorNode);
                    lNodes.push_back(lOther);
                    SelectNodes(lNodes);
                }
            }

            SNode *lOld=mCursorNode;
            mCursorNode=lOther;

            if(mRenderer)
            {
                OnCursorChanged(lOld);
                OnCursorChanged(mCursorNode);
            }
            EnsureVisible(lOther,true);
        }
    }
    else if(pE.mKey==KEY_SPACE)
    {
        if(!mCursorNode) return;

        if(pE.mMods&KEYMOD_CONTROL)
        {
            ToggleNode(mCursorNode);
        }
        else if(!mCursorNode->mSelected||mTWSelected>1)
        {
            SelectNode(mCursorNode);
        }
    }
}

void CTreeBox::OnSize(const CGeometryEvent &pE)
{
    if(mRenderer)
        mRenderer->OnSize(pE);
}

void CTreeBox::OnStyleChanged(TStyle)
{
    if(mRenderer)
        mRenderer->OnBigChange();
}

void CTreeBox::OnNodeSelected(SNode *pItem)
{
    if(mRenderer) mRenderer->OnNodeSelected(pItem);

    if(pItem->mSelected)
        sSelected(pItem);
    else
        sUnselected(pItem);
}

void CTreeBox::OnNodeChanged(SNode *pItem)
{
    if(mRenderer) mRenderer->OnNodeChanged(pItem);
}

void CTreeBox::OnCursorChanged(SNode *pNode)
{
    if(mRenderer) mRenderer->OnCursorChanged(pNode);
    if(mCursorNode==pNode)
        eCursorChanged(pNode);
}

void CTreeBox::OnNodeExpanded(SNode*)
{
    if(mRenderer) mRenderer->OnBigChange();
}

void CTreeBox::OnNodeRemoved(SNode *pNode)
{
    if(mCursorNode==pNode)
        mCursorNode=nullptr;

    if(mRenderer) mRenderer->OnNodeRemoved(pNode);
}

void CTreeBox::OnNodeAdded(SNode *pNode)
{
    if(mRenderer) mRenderer->OnNodeAdded(pNode);
}

void CTreeBox::OnFieldsChanged()
{
    if(mRenderer) mRenderer->OnFieldsChanged();
}

void CTreeBox::OnDropEnter(CDrop *pDrop,const CPoint_d &pPoint)
{
    mDropping.mInside=true;
    SNode *lNode=mRenderer->GetNodeAt(pPoint);

    mDropping.mLast=lNode;
    mDropping.mAccept=WillAccept(lNode,pDrop);
    if(mDropping.mAccept)
    {
        pDrop->Accept();
    }
    else
    {
        pDrop->Reject();
    }
    Refresh();
}

void CTreeBox::OnDropLeave(CDrop*,const CPoint_d&)
{
    mDropping.mInside=false;
    Refresh();
}

void CTreeBox::OnDropMove(CDrop *pDrop,const CPoint_d &pPoint)
{
    SNode *lNode=mRenderer->GetNodeAt(pPoint);
    if(lNode==mDropping.mLast) return;

    mDropping.mLast=lNode;
    mDropping.mAccept=WillAccept(lNode,pDrop);
    if(mDropping.mAccept)
    {
        pDrop->Accept();
    }
    else
    {
        pDrop->Reject();
    }
    Refresh();
}

void CTreeBox::OnDropCancel(CDrop*,const CPoint_d&)
{
    mDropping.mInside=false;
    Refresh();
}

void CTreeBox::OnDropDrop(CDrop *pDrop,const CPoint_d &pPoint,EDNDActions pAction)
{
    SNode *lNode=mRenderer->GetNodeAt(pPoint);

    OnDrop(lNode,pDrop,pAction);

    mDropping.mInside=false;
    Refresh();
}

/*namespace gui*/ } /*namespace buola*/ }
